/*
 * 
 */
(function() {

	function CanvasDrawer(canvas, universe, color, pix) {
		this.context = canvas.getContext("2d");
		canvas.width = universe.sx * pix;
		canvas.height = universe.sy * pix;
		this.universe = universe;
		this.color = color;
		this.pix = pix;
	}

	CanvasDrawer.prototype = {
		draw: function() {
			var sx = this.universe.sx;
			var sy = this.universe.sy;
			var universe = this.universe;
			var c = this.context;
			var cl = this.color;
			var pix = this.pix;
			c.fillStyle = cl.background();
			c.fillRect(0, 0, sx * pix, sy * pix);
			var lastStatus = 0;
			var gx = 0;
			var gy = 0;
			for (var y = 0; y < sy; y++) {
				gx = 0;
				for (var x = 0; x < sx; x++) {
					var s = cl.get(x, y, universe);
					if (s) {
						if (lastStatus != s) {
							c.fillStyle = s;
							lastStatus = s; 
						}
						c.fillRect(gx, gy, pix, pix);
					}
					gx += pix;
				}
				gy += pix;
			}
		},
	};


	/*
	 * Color package
	 */
	var Color = {};

	/*
	 * RgbColor
	 */
	function RgbColor(r, g, b) {
		this.red = r;
		this.green = g;
		this.blue = b;
	}

	RgbColor.prototype = {
		toString: function() {
			var n = ((parseInt(this.red) & 0xff) << 16) |
				((parseInt(this.green) & 0xff) << 8) |
				(parseInt(this.blue) & 0xff);
			n = n.toString(16);
			return "#" + "000000".substr(0, 6 - n.length) + n;
		},
	
		add: function(rgbColor) {
			return new RgbColor(
				this.red + rgbColor.red,
				this.green + rgbColor.green,
				this.blue + rgbColor.blue);
		},

		sub: function(rgbColor) {
			return new RgbColor(
				this.red - rgbColor.red,
				this.green - rgbColor.green,
				this.blue - rgbColor.blue);
		},

		div: function(number) {
			return new RgbColor(
				this.red / number,
				this.green / number,
				this.blue / number);
		},

		mul: function(number) {
			return new RgbColor(
				this.red * number,
				this.green * number,
				this.blue * number);
		},
	};

	RgbColor.parse = function(color) {
		return new RgbColor(
			(color & 0xff0000) >> 16,
			(color & 0xff00) >> 8,
			(color & 0xff));
	}
	
	/*
	 * GradationalColor
	 */
	function GradationalColor(backgroundColor, fromColor, toColor) {
		this.backgroundColor = RgbColor.parse(backgroundColor).toString();
		this.fromColor =  RgbColor.parse(fromColor);
		this.toColor =  RgbColor.parse(toColor);
	}

	GradationalColor.prototype = {
		create: function(universe) {
			var colors = [];
			var len = universe.rule.valus.length;
			var c = this.fromColor;
			var s = this.toColor.sub(this.fromColor).div(len);
			for (var i = 1; i <= len; i++) {
				colors[i] = c.toString();
				c = c.add(s);
			}
			return new GradationalColorSelector(this.backgroundColor, colors, universe);
		},
	};
	
	function GradationalColorSelector(backgroundColor, colors, universe) {
		this.backgroundColor = backgroundColor;
		this.colors = colors;
		this.universe = universe;
	}

	GradationalColorSelector.prototype = {
		get: function(x, y) {
			var s = this.universe.get(x, y);
			if (0 == s) return null; 
			return this.colors[s];
		},
	
		background: function() {
			return this.backgroundColor;
		},
	};

	Color.GradationalColor = GradationalColor;

	/*
	 * ArrayColor
	 */
	function ArrayColor(backgroundColor, colors) {
		this.backgroundColor = backgroundColor;
		this.colors =  colors;
	}
	
	ArrayColor.prototype = {
		create: function(universe) {
			return new GradationalColorSelector(this.backgroundColor, this.colors, universe);
		},
	};

	Color.ArrayColor = ArrayColor;
	
	/*
	 * RepeatedColor 
	 */
	function RepeatedColor(colors) {
		this.colors = colors;
	}

	RepeatedColor.prototype = {
		create: function(universe) {
			return new RepeatedColorSelector(this.colors, universe);
		},
	};

	function RepeatedColorSelector(colors, universe) {
		this.colors = colors;
		this.universe = universe;
	}

	RepeatedColorSelector.prototype = {
		get: function(x, y) {
			var s = this.universe.get(x, y);
			if (0 == s) return null; 
			return this.colors[s] || this.colors[s % (this.colors.length - 1) + 1];
		},

		background: function() {
			return this.colors[0];
		},
	};

	Color.RepeatedColor = RepeatedColor;

	/*
	 * EightFourSumColor
	 * the length of colors is 26.
	 * the zero is dead color.
	 */
	function EightFourSumColor(colors) {
		this.colors = colors;
	}

	EightFourSumColor.prototype.create = function(universe) {
		return new EightFourSumColorSelector(this.colors, universe);
	};
  
	function EightFourSumColorSelector(colors, universe) {
		this.colors = colors;
		this.universe = universe;
		this.universeColor = new akinao.ca.UniverseClosed(universe.sx, universe.sy);
	}

	EIGHT_FOUR_COLOR_INDEX = [1, 2, 4, 7, 11, 16, 20, 22, 25];

	var moreThanZeroFilter = akinao.ca.Filter.moreThanZero;
	
	EightFourSumColorSelector.prototype = {
		get: function(x, y) {
			var s = this.universe.get(x, y);
			if (0 == s) {
				this.universeColor.set(x, y, 0);
				return null;
			}
			var c = this.universeColor.get(x, y)
			if (0 == c) {
				var st = this.universe.getNeighborhood(x, y, moreThanZeroFilter);
				c = EIGHT_FOUR_COLOR_INDEX[st.total8] + st.total4;
				this.universeColor.set(x, y, c);
			}
			return this.colors[c];
		},

		background: function() {
			return this.colors[0];
		},
	}

	Color.EightFourSumColor = EightFourSumColor;

	Color.palette = {
		panton: new Color.RepeatedColor([
			"black",
			"orange",
			"tomato",
			"red",
			"crimson",
			"purple",
			"indigo",
			"navy",
			"blue",
			"darkgreen",
		]),
		ringLamp: new Color.RepeatedColor([
			"black",
			"floralwhite",
			"red",
			"coral",
			"maroon",
			"gold",
			"crimson",
		]),
		monotone: new Color.RepeatedColor([
			"whitesmoke",
			"gray",
			"gainsboro",
			"darkgray",
			"lightgray",
			"dimgray",
		]),
		gradation: new Color.GradationalColor(
			0xf0f0f0,
			0x606060,
			0xffffff
		),
		block: new Color.EightFourSumColor([
			"whitesmoke", // background
			"moccasin", // 0a
			"skyblue", // 1a
			"deepskyblue", // 1b
			"blue", // 2a
			"mediumblue", // 2b
			"dodgerblue", // 2c
			"green", // 3a
			"darkgreen", // 3b
			"forestgreen", // 3c
			"limegreen", // 3d
			"coral", // 4a
			"orangered", // 4b
			"red", // 4c
			"crimson", // 4d
			"maroon", // 4e
			"yellow", // 5b
			"gold", // 5c
			"orange", // 5d
			"darkorange", // 5e
			"purple", // 6c
			"indigo", // 6d
			"magenta", // 6e
			"darkcyan", // 7d
			"darkslategray", // 7e
			"gray", // 8e
		]),
		blockMono: new Color.EightFourSumColor([
   			"#ffffff", // background
   			"#707070", // 0a
   			"#808080", // 1a
   			"#888888", // 1b
   			"#909090", // 2a
   			"#969696", // 2b
   			"#9b9b9b", // 2c
   			"#a0a0a0", // 3a
   			"#a4a4a4", // 3b
   			"#a8a8a8", // 3c
   			"#acacac", // 3d
   			"#b0b0b0", // 4a
   			"#b3b3b3", // 4b
   			"#b6b6b6", // 4c
   			"#b9b9b9", // 4d
   			"#bbbbbb", // 4e
   			"#c0c0c0", // 5b
   			"#c4c4c4", // 5c
   			"#c8c8c8", // 5d
   			"#cccccc", // 5e
   			"#d0d0d0", // 6c
   			"#d6d6d6", // 6d
   			"#dbdbdb", // 6e
   			"#e0e0e0", // 7d
   			"#e8e8e8", // 7e
   			"#f0f0f0", // 8e
   		]),
   		simple: new ArrayColor("#ffffee", {1: "#f0f0e0"}),
   		simpleBlack: new ArrayColor("#000", {1: "#333"}),
   		simpleWhite: new ArrayColor("#fff", {1: "#888"}),
   		simpleBlue: new ArrayColor("#fff", {1: "#cef"}),
	};
	
	/*
	 * object {
	 *     element: window.element
	 *     universe: {x: Int, y: Int, closed: boolean},
	 *     rule: function(x, y, m),
	 *     color: Color,
	 *     pix: Int
	 * }
	 */
	function CellAutomata(object) {
		this.universe = new akinao.ca.Universe(object.universe.x, object.universe.y, object.universe.closed, object.rule);
		this.drawer = new CanvasDrawer(object.element, this.universe, object.color.create(this.universe), object.pix);
	}

	CellAutomata.prototype = {
		set: function(rule) {
			this.universe.next(rule);
		},
	
		draw: function() {
			this.drawer.draw();
		},
	
		next: function(rule) {
			this.set(rule);
			this.draw();
		},
	};

	akinao.lang.namespace("akinao.ca", {
		CellAutomata: CellAutomata,
		Color: Color,
	});

})();